use cmsis_rtos2_rs::osWaitForever;
use cmsis_rtos2_rs::os_delay;
use cmsis_rtos2_rs::static_mem::EventFlags;
use driver_utils::service::simple_step_motor::Dir;
use driver_utils::service::simple_step_motor::StepMotor;
use driver_utils::service::simple_step_motor::StepMotorDrv;
use periph_utils::stm32f1;
use periph_utils::stm32f1::stm32f103::NVIC;
use periph_utils::stm32f1::stm32f103::{interrupt, Interrupt};
use periph_utils::GpioConfig;
use periph_utils::TimPwm;
use utils::MutCell;

use crate::cp_heat::HeatErr;

use super::sensor;

#[derive(GpioConfig, TimPwm)]
#[gpio(
    init = "gpio_init",
    io(name = "dir", io = "PC3", mode = "OUT_PP", set, reset),
    io(name = "enable", io = "PC1", mode = "OUT_PP", set, reset),
    io(name = "alm", io = "PC0", mode = "IN_FLOAT", read)
)]
#[pwm(
    init = "pwm_init",
    tim = "TIM5",
    freq = 10000,
    set_freq,
    enable,
    disable,
    irq,
    ch(ch = 2, duty = 50, mode = 1)
)]
struct Init;

#[derive(GpioConfig)]
#[gpio(
    init = "gpio_init",
    io(name = "puls", io = "PA1", mode = "OUT_PP", set, reset)
)]
struct OutInit;

#[derive(GpioConfig)]
#[gpio(init = "gpio_init", io(name = "puls", io = "PA1", mode = "AF_PP"))]
struct AFInit;

const FLAG_FINISH: u32 = 0x01;

struct Context {
    event_flags: EventFlags,
    motor: MutCell<StepMotor<Init>>,
}

impl Context {
    const fn uninit() -> Self {
        Self {
            event_flags: EventFlags::uninit(),
            motor: MutCell::const_new(StepMotor::const_new(Init)),
        }
    }

    unsafe fn init_once(&self) {
        self.event_flags.init_once("move_motor");
        self.event_flags.set_flags(FLAG_FINISH);
    }
}

static CTX: Context = Context::uninit();

pub unsafe fn init() {
    Init::gpio_init();
    Init::pwm_init();
    OutInit::gpio_init();

    CTX.init_once();
    NVIC::unmask(Interrupt::TIM5);
}

pub fn run(position: u32) -> Result<(), HeatErr> {
    let motor = unsafe { CTX.motor.as_mut() };
    let r = motor.run_position(position as i32);
    Init::dir_set();
    r
}

pub fn is_alm() -> bool {
    Init::is_alm()
}

impl StepMotorDrv for Init {
    type Error = HeatErr;
    const ALM_ERR: Self::Error = HeatErr::MoveMotorAlm;
    const TIMEOUT_ERR: Self::Error = HeatErr::MoveMotorTimeout;
    const ACC: u32 = 2;
    const BASE_FREQ: u32 = 1500;
    const RESET_FREQ: u32 = 2000;
    const VAR_RANGE: u32 = 1000;
    const RESET_TIMEOUT: u32 = 20 * 1000;

    fn delay(tick: u32) {
        os_delay(tick);
    }

    fn disable_pwm() {
        Init::tim5_pwm_disable_irq();
        Init::tim5_pwm_disable();
        OutInit::gpio_init_no_rcc();
    }

    fn enable_pwm(freq: u32) {
        AFInit::gpio_init_no_rcc();
        Init::tim5_pwm_set_freq(freq);
        Init::tim5_pwm_enable_irq();
        Init::tim5_pwm_enable();
    }

    fn enable_pwm_not_irq(freq: u32) {
        AFInit::gpio_init_no_rcc();
        Init::tim5_pwm_set_freq(freq);
        Init::tim5_pwm_disable_irq();
        Init::tim5_pwm_enable();
    }

    fn is_alm() -> bool {
        !Init::alm_read()
    }

    fn is_origin() -> bool {
        sensor::is_nozzle_origin()
    }

    fn set_dir(dir: Dir) {
        match dir {
            Dir::Exit => Init::dir_set(),
            Dir::Enter => Init::dir_reset(),
        }
    }

    fn set_puls_pin() {
        OutInit::puls_set();
    }

    fn reset_puls_pin() {
        OutInit::puls_reset();
    }

    fn set_pwm_freq(freq: u32) {
        Init::tim5_pwm_set_freq(freq);
    }

    fn notify_run_finish() {
        CTX.event_flags.set_flags(FLAG_FINISH);
    }

    fn wait_run_finish() {
        _ = CTX.event_flags.wait_any_flags(FLAG_FINISH, osWaitForever);
    }
}

#[interrupt]
unsafe fn TIM5() {
    if Init::tim5_is_irq() {
        Init::tim5_clear_irq();
        CTX.motor.as_mut().on_puls();
    }
}
